package com.wmx.spring;

import org.junit.Test;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * springframework BeanUtils Java bean 内审与反射工具类
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/12/7 18:55
 */
public class BeanUtilsTest {

    /**
     * copyProperties(Object source, Object target)：将给定源 bean 的属性值复制到目标 bean 中。
     * copyProperties(Object source, Object target, Class<?> editable)
     * copyProperties(Object source, Object target, String... ignoreProperties)
     * 1、只要属性匹配，源类和目标类就不必相互匹配，甚至不必相互派生。
     * 2、源 bean 公开但目标 bean 不公开的任何 bean 属性都将被忽略。
     * 3、只能是单个 POJO 对象，不能是 List<POJO>，也不能是 Map 对象.
     * 4、只能是 Java bean 直接的属性赋值，不能 Java bean -> Map，或者 Map -> Java Bean。
     * 5、editable：表示只设置在给定“可编辑”类（或接口）中定义的属性。
     * 6、ignoreProperties：表示忽略某些属性，不进行复制，则目标 bean 的这些属性会为 null.
     */
    @Test
    public void testCopyProperties() {
        Book book = new Book("a1", "三国演义", 78.88F, new Date(), true, 1000);
        Book book1 = new Book();
        BeanUtils.copyProperties(book, book1, "publishDate", "stock");

        book.setStock(999);
        book1.setPrice(88.88F);
        //Book{bid='a1', title='三国演义', price=78.88, publishDate=Tue Dec 07 19:10:20 CST 2021, isShow=true, stock=999}
        System.out.println(book);
        //Book{bid='a1', title='三国演义', price=88.88, publishDate=Tue Dec 07 19:10:20 CST 2021, isShow=true, stock=1000}
        System.out.println(book1);
    }


    /**
     * Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes)
     * 1、查找具有给定方法名称和给定参数类型的方法，该方法在给定类或其一个超类上声明。
     * 2、将返回公共、受保护、包访问或。无法获取 私有方法
     * 3、检查 Class.getDeclaredMethod，向上级联到所有超类。
     * 4、返回方法对象，如果找不到，则返回 null
     */
    @Test
    public void testFindDeclaredMethod() throws Exception {
        Book book = Book.class.newInstance();

        Method setBidMethod = BeanUtils.findDeclaredMethod(Book.class, "setTitle", String.class);
        Method getBidMethod = BeanUtils.findDeclaredMethod(Book.class, "getTile", null);

        setBidMethod.invoke(book, "UI8888999P12");
        Object invoke = getBidMethod.invoke(book);
        //UI8888999P12
        System.out.println(invoke);
    }

    /**
     * Method findDeclaredMethodWithMinimalParameters(Class<?> clazz, String methodName)
     * 1、查找具有给定方法名称和最小参数(最佳情况是没有参数none)的方法，该方法在给定类或其一个超类上声明。将返回公共、受保护、包访问。无法获取 私有方法
     * 2、检查{@code Class.getDeclaredMethods}，向上级联到所有超类。
     * 3、如果找到了具有给定名称的方法，但无法解析为具有最小参数的唯一方法，则引发IllegalArgumentException
     *
     * @throws Exception
     */
    @Test
    public void testFindDeclaredMethodWithMinimalParameters() throws Exception {
        Book book = Book.class.newInstance();

        Method setBidMethod = BeanUtils.findDeclaredMethodWithMinimalParameters(Book.class, "setTitle");
        Method getBidMethod = BeanUtils.findDeclaredMethodWithMinimalParameters(Book.class, "getTitle");

        setBidMethod.invoke(book, "UI8888999P");
        Object invoke = getBidMethod.invoke(book);
        //UI8888999P
        System.out.println(invoke);
    }

    /**
     * Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes)
     * <p>
     * 1、查找具有给定方法名称和给定参数类型的方法，该方法在给定类或其一个超类上声明。首选公共方法，但也将返回受保护、包访问或私有方法。
     * 2、首先检查{@code Class.getMethod}，返回到 {@code findDeclaredMethod}。这样即使在 Java 安全设置受限的环境中，也可以找到没有问题的公共方法。
     * 3、返回方法对象，如果找不到，则返回{@code null}
     */
    @Test
    public void testFindMethod() throws Exception {
        Book book = Book.class.newInstance();

        Method setBidMethod = BeanUtils.findMethod(Book.class, "setTitle", String.class);
        Method getBidMethod = BeanUtils.findMethod(Book.class, "toString");

        setBidMethod.invoke(book, "水浒传");
        Object invoke = getBidMethod.invoke(book);
        //Book{bid='null', title='水浒传', price=null, publishDate=null, isShow=null, stock=null}
        System.out.println(invoke);
    }

    /**
     * Method findMethodWithMinimalParameters(Class<?> clazz, String methodName)
     * 1、查找具有给定方法名称和最小参数（最佳情况：none）的方法，该方法在给定类或其一个超类上声明。首选公共方法，但也将返回受保护、包访问或私有方法。
     * 2、首先检查{@code Class.getMethods}，返回到{@code findDeclaredMethodWithMinimalParameters}。这使得即使在具有受限Java安全设置的环境中也可以找到没有问题的公共方法。
     * 3、如果找到了具有给定名称的方法，但无法解析为具有最小参数的唯一方法，则引发IllegalArgumentException
     */
    @Test
    public void testFindMethodWithMinimalParameters() throws Exception {
        Book book = Book.class.newInstance();

        Method setBidMethod = BeanUtils.findMethodWithMinimalParameters(Book.class, "setTitle");
        Method getBidMethod = BeanUtils.findMethodWithMinimalParameters(Book.class, "toString");

        setBidMethod.invoke(book, "水浒传");
        Object invoke = getBidMethod.invoke(book);
        //Book{bid='null', title='水浒传', price=null, publishDate=null, isShow=null, stock=null}
        System.out.println(invoke);
    }

    /**
     * Method findMethodWithMinimalParameters(Method[] methods, String methodName):在给定的方法列表中查找具有给定方法名称和最小参数（最佳情况：无）的方法。
     * Constructor<T> findPrimaryConstructor(Class<T> clazz):返回所提供类的主构造函数
     * T instantiateClass(Class<?> clazz, Class<T> assignableTo)
     * T instantiateClass(Class<T> clazz)
     * T instantiateClass(Constructor<T> ctor, Object... args)
     * 1、使用其无参数构造函数实例化一个类(clazz)，并将新实例作为指定的可赋值类型(assignableTo)返回。
     * 2、在要实例化的类（clazz）的类型不可用，但所需的类型(assignableTo)已知的情况下非常有用。
     * 3、请注意，如果给定了不可访问（即非公共）构造函数，此方法将尝试将构造函数设置为可访问。
     * 4、如果无法实例化 bean，则抛出 BeanInstationException
     * 5、clazz 必须与 assignableTo 相同，或是 assignableTo 的子类
     */
    @Test
    public void testInstantiateClass() {
        List list = BeanUtils.instantiateClass(ArrayList.class, List.class);
        list.add(111);
        //[111]
        System.out.println(list);

        ArrayList arrayList = BeanUtils.instantiateClass(ArrayList.class);
        arrayList.add(9999);
        //[9999]
        System.out.println(arrayList);

        Map map = BeanUtils.instantiateClass(Map.class);
        System.out.println(map);

    }


}
